package gl.drawer

import org.lwjgl.opengl.ARBSparseTexture.*
import org.lwjgl.opengl.GL45C.*
import org.lwjgl.stb.STBImage.stbi_image_free
import org.lwjgl.stb.STBImage.stbi_load

class Sparse: Drawer() {

    private var texture = 0
    private var pageWidth = 0
    private var pageHeight = 0

    private var program = 0

    private var vao = 0
    private var vetBuffer = 0

    override fun initialize() {
        // texture
        val width0 = IntArray(1)
        val height0 = IntArray(1)
        val image0 = stbi_load("D:\\lq\\gl\\app\\src\\main\\resources\\icon.png", width0, height0, IntArray(1), 4)
        check(image0 != null) { "Can't load image0" }
        val width1 = IntArray(1)
        val height1 = IntArray(1)
        val image1 = stbi_load("D:\\lq\\gl\\app\\src\\main\\resources\\image.jpg", width0, height0, IntArray(1), 4)
        check(image1 != null) { "Can't load image1" }
        texture = glGenTextures()
        glBindTexture(GL_TEXTURE_2D, texture)
        glTextureParameterIi(texture, GL_TEXTURE_SPARSE_ARB, GL_TRUE)
        glTextureStorage2D(GL_TEXTURE_2D, 1, GL_RGBA4, 2048, 2048)
        val imageBuffers = IntArray(2)
        // texture - page
        val pageTypeCount = glGetInternalformati(GL_TEXTURE_2D, GL_RGBA4, GL_NUM_VIRTUAL_PAGE_SIZES_ARB)
        check(pageTypeCount > 0) { "Unsupport sparse texture" }
        val pageWidths = IntArray(pageTypeCount)
        val pageHeights = IntArray(pageTypeCount)
        glGetInternalformativ(GL_TEXTURE_2D, GL_RGBA4, GL_VIRTUAL_PAGE_SIZE_X_ARB, pageWidths)
        glGetInternalformativ(GL_TEXTURE_2D, GL_RGBA4, GL_VIRTUAL_PAGE_SIZE_Y_ARB, pageHeights)
        pageWidth = pageWidths.last()
        pageHeight = pageHeights.last()
        for (i in 0 until pageTypeCount) {
            val pageW = pageWidths[i]
            val pageH = pageHeights[i]
            if (pageW >= width0[0] && pageW >= width1[0] && pageH >= height0[0] && pageH >= height1[0]) {
                pageWidth = pageW
                pageHeight = pageH
                break
            }
        }
        // texture - page0
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, imageBuffers[0])
        glBufferStorage(GL_PIXEL_UNPACK_BUFFER, image0, 0)
        stbi_image_free(image0)
        glTexPageCommitmentARB(GL_TEXTURE_2D, 0, 0, 0, 0, pageWidth, pageHeight, 0, true)
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width0[0], height0[0], GL_RGBA, GL_UNSIGNED_BYTE, 0)
        // texture - page1
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, imageBuffers[1])
        glBufferStorage(GL_PIXEL_UNPACK_BUFFER, image1, 0)
        stbi_image_free(image1)
        glTexPageCommitmentARB(GL_TEXTURE_2D, 0, pageWidth, pageHeight, 0, pageWidth, pageHeight, 0, true)
        glTexSubImage2D(GL_TEXTURE_2D, 0, pageWidth, pageHeight, width1[0], height1[0], GL_RGBA, GL_UNSIGNED_BYTE, 0)
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)
        glDeleteBuffers(imageBuffers)
        // program
        program = glCreateProgram()
        val vs = glCreateShader(GL_VERTEX_SHADER)
        glShaderSource(vs, vertexShader)
        glCompileShader(vs)
        check(glGetShaderi(vs, GL_COMPILE_STATUS) == GL_TRUE) { glGetShaderInfoLog(vs) }
        val fs = glCreateShader(GL_FRAGMENT_SHADER)
        glShaderSource(fs, fragmentShader)
        check(glGetShaderi(fs, GL_COMPILE_STATUS) == GL_TRUE) { glGetShaderInfoLog(fs) }
        glAttachShader(program, vs)
        glAttachShader(program, fs)
        glLinkProgram(program)
        glDetachShader(program, vs)
        glDetachShader(program, fs)
        glDeleteShader(vs)
        glDeleteShader(fs)
        check(glGetProgrami(fs, GL_LINK_STATUS) == GL_TRUE) { glGetProgramInfoLog(program) }
        glUseProgram(program)
        // vertex
        vetBuffer = glGenBuffers()
        glBindBuffer(GL_ARRAY_BUFFER, vetBuffer)
        val v00 = 0f
        val u00 = 0f
        val u01 = width0[0] / 2048f
        val v01 = height0[0] / 2048f
        val u10 = pageWidth / 2048f
        val u11 = u10 + width1[0] / 2048f
        val v10 = 0f
        val v11 = height1[0] / 2048f
        glBufferStorage(GL_ARRAY_BUFFER, floatArrayOf(
            // left
            -0.9f, -0.4f, u00, v01,
            -0.1f, -0.4f, u01, v01,
            -0.1f,  0.4f, u01, v00,
            -0.9f,  0.4f, u00, v00,
            // right
            0.1f, -0.4f, u10, v11,
            0.9f, -0.4f, u11, v11,
            0.9f,  0.4f, u11, v10,
            0.1f,  0.4f, u10, v10,
        ), 0)
        vao = glGenVertexArrays()
        glBindVertexArray(vao)
        glEnableVertexAttribArray(0)
        glEnableVertexAttribArray(1)
        glDisableVertexAttribArray(2)
        glVertexAttribPointer(0, 2, GL_FLOAT, false, 16, 0)
        glVertexAttribPointer(1, 2, GL_FLOAT, false, 16, 8)
        glVertexAttribI1i(1, 0)
    }

    override fun update(): Boolean {
        super.update()
        glTexPageCommitmentARB(GL_TEXTURE_2D, 0, 0, 0, 0, pageWidth, pageHeight, 0, true)
        glTexPageCommitmentARB(GL_TEXTURE_2D, 0, pageWidth, pageHeight, 0, pageWidth, pageHeight, 0, false)
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
        glTexPageCommitmentARB(GL_TEXTURE_2D, 0, 0, 0, 0, pageWidth, pageHeight, 0, false)
        glTexPageCommitmentARB(GL_TEXTURE_2D, 0, pageWidth, pageHeight, 0, pageWidth, pageHeight, 0, true)
        glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
        return false
    }

    override fun terminal() {
        // vertex
        glBindVertexArray(0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glDeleteBuffers(0)
        glDeleteVertexArrays(vao)
        // program
        glUseProgram(0)
        glDeleteProgram(program)
        // image
        glBindTexture(GL_TEXTURE_2D, 0)
        glDeleteTextures(texture)
    }
}

private val vertexShader = """
#version 400 core

layout(location=0) in vec2 vPosition;
layout(location=1) in vec2 vTexCoord;
layout(location=2) in sampler2D vTex;

out vec2 coord;
out sampler2D tex;

void main() {
    coord = vTexCoord;
    tex = vTex;
    gl_Position = vec4(vPosition, 0, 1);
}
""".trimIndent()

private val fragmentShader = """
#version 400 core

in vec2 coord;
in sampler2D tex;
out vec4 fColor;

void main() {
    fColor = texture(tex, coord);
}
""".trimIndent()
